#include "stdafx.h"
#include "texture2d.h"

#include <QtGui/QImage>

namespace GLC {

class Texture2DOpenGLLoader
{
public:
    static Texture2DOpenGLLoader &instance()
    {
        static Texture2DOpenGLLoader loader;
        return loader;
    }

    void bind(GLuint texture)
    {
        if (_bindedTexture != texture)
        {
            _bindedTexture = texture;
            glBindTexture(GL_TEXTURE_2D, texture);
        }
    }

    void unbind()
    {
        _bindedTexture = 0;
        glBindTexture(GL_TEXTURE_2D, 0);
    }

    GLuint query(const std::string &name)
    {
        auto item = _loadedTextures.find(name);
        if (item != _loadedTextures.end())
        {
            auto &pair = item->second;
            ++pair.second;
            return pair.first;
        }
        {
            std::pair<GLuint, int> pair;
            pair.first = loadTexture(name);
            // There are one reference to texture object
            pair.second = 1;
            _loadedTextures[name] = pair;
            return pair.first;
        }
    }

    void release(const std::string &name)
    {
        auto item = _loadedTextures.find(name);
        if (item != _loadedTextures.end())
        {
            auto &pair = item->second;
            --pair.second;
            if (pair.second == 0)
            {
                if (pair.first == _bindedTexture)
                    unbind();
                glDeleteTextures(1, &pair.first);
                _loadedTextures.erase(item);
            }
        }
        else
        {
            myDebug() << "GLC::Texture2D error: attempt to delete non-existing texture"
                      << name << "\n";
        }
    }

private:
    GLuint loadTexture(const std::string &path)
    {
        QImage image;
        {
            QImage temporary(path.c_str(), 0);
            if (temporary.isNull())
            {
                myDebug() << "Texture file \'" << path << "\' can't be loaded\n";
                return 0;
            }
            if (temporary.hasAlphaChannel())
                image = temporary.convertToFormat(QImage::Format_ARGB32);
            else
                image = temporary.convertToFormat(QImage::Format_RGB32);
            if (image.isNull())
            {
                myDebug() << "Texture file \'" << path << "\' can't be converted\n";
                return 0;
            }
        }

        GLuint texture;
        glPixelStorei(GL_UNPACK_ALIGNMENT,4);
        glGenTextures(1, &texture);
        glBindTexture(GL_TEXTURE_2D, texture);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

        // set anisotropic filtration level for this texture
        // (only if extension available)
//        if (GL_EXT_texture_filter_anisotropic)
//        {
//            GLint maxAnisotropy = 0;
//            glGetIntegerv(
//                GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT,
//                &maxAnisotropy);
//            glTexParameteri(
//                GL_TEXTURE_2D,
//                GL_TEXTURE_MAX_ANISOTROPY_EXT,
//                maxAnisotropy);
//        }

        size_t internalFormat = (image.hasAlphaChannel()) ? 4 : 3;
        gluBuild2DMipmaps(GL_TEXTURE_2D, internalFormat,
                          image.width(), image.height(),
                          GL_BGRA, GL_UNSIGNED_BYTE,
                          image.bits());
        glBindTexture(GL_TEXTURE_2D, _bindedTexture);

        return texture;
    }

    GLuint _bindedTexture;
    std::map<std::string,  std::pair<GLuint, int> > _loadedTextures;
};

Texture2D::Texture2D(const std::string &name)
    :_name(name)
{
    _texture = Texture2DOpenGLLoader::instance().query(_name);
}

Texture2D::~Texture2D()
{
    _texture = 0;
    Texture2DOpenGLLoader::instance().release(_name);
}

void Texture2D::bind() const
{
    Texture2DOpenGLLoader::instance().bind(_texture);
}

void Texture2D::unbind()
{
    Texture2DOpenGLLoader::instance().unbind();
}

const std::string &Texture2D::name() const
{
    return _name;
}

uintptr_t Texture2D::getHandle() const
{
    return _texture;
}

Texture2DRef Texture2DRef::createNull()
{
    return Texture2DRef();
}

Texture2DRef::Texture2DRef(const Texture2D &source)
    :_texture(source._texture)
{
}

void Texture2DRef::bind() const
{
    Texture2DOpenGLLoader::instance().bind(_texture);
}

void Texture2DRef::unbind()
{
    Texture2DOpenGLLoader::instance().unbind();
}

bool Texture2DRef::isNull() const
{
    return (_texture == 0);
}

uintptr_t Texture2DRef::getHandle() const
{
    return _texture;
}

Texture2DRef::Texture2DRef()
    :_texture(0)
{
}

} // namespace GLC
